<!DOCTYPE html>
<title>Test that stalled, timeupdate and waiting events are sent when media load stalls in the middle.</title>
<video id="test_video"></video>
<div id="log_console"></div>
<script src=../../media-resources/media-file.js></script>
<script src="/w3c/resources/testharness.js"></script>
<script src="/w3c/resources/testharnessreport.js"></script>
<script>
async_test(t => {
    let video = document.getElementById('test_video');
    let log_console = document.getElementById('log_console');

    // For debugging.
    function logMessage(msg) {
        let span = document.createElement("span");
        span.innerHTML = msg + '<br>';
        log_console.appendChild(span);
    };

    function logEvent(e) {
        logMessage('event: ' + e.type);
    };

    playback_ew = new EventWatcher(t, video, [
        'canplay',
        'canplaythrough',
        'durationchange',
        'loadedmetadata',
        'loadeddata',
        'play',
        'playing',
        'waiting']);

    // The stalled event needs a separate watcher as it can be fired at any
    // point during the sequence of other playback events. Stalled is triggered
    // by prolonged network inactivity.
    stalled_ew = new EventWatcher(t, video, [
        'stalled']);

    // This helper is an alternative to EventWatcher for events that fire on a
    // recurring basis. EventWatcher is not suitable because you must always be
    // "waiting" for the event to fire every time, whereas this method allows
    // you to just verify that it fired once and move on.
    function waitForRecurringEvent(name) {
        let resolve_cb;
        let promise = new Promise(function(resolve, reject) {
            resolve_cb = resolve;
        });
        video.addEventListener(name, t.step_func((evt) => resolve_cb(evt)));
        return promise;
    }

    // Monitor timeupdate w.r.t. readyState throughout test. Time should not
    // advance while readyState <= HAVE_CURRENT_DATA.
    let timeAtStartOfWaiting = null;
    video.addEventListener('timeupdate', t.step_func(function(e) {
        logMessage('event: timeupdate at time: ' + e.target.currentTime +'  readystate:' + e.target.readyState);
        if (e.target.readyState <= HTMLMediaElement.HAVE_CURRENT_DATA) {
            if (timeAtStartOfWaiting == null) {
                // Note the time when waiting begins.
                timeAtStartOfWaiting = e.target.currentTime;
            } else {
                // Verify that current time is not advancing while waiting.
                assert_equals(e.target.currentTime, timeAtStartOfWaiting);
            }
        } else if (timeAtStartOfWaiting != null) {
            // Waiting has ended, so clear timeAtStartOfWaiting.
            timeAtStartOfWaiting = null;
        }
     }));

    // NOTE: Event sequence verification is achieved by chaining together
    // promises via then(). To verify separate parallel event sequences (e.g.
    // playback vs network), we setup separate chains of promises. Promise.all
    // ensures that all separate sequences complete.

    // Verify playback progresses then runs out of data.
    Promise.all([
        // First wait for the resource to load.
        playback_ew.wait_for('durationchange').then(logEvent)
            .then(() => playback_ew.wait_for('loadedmetadata')).then(logEvent)
            .then(() => playback_ew.wait_for('loadeddata')).then(logEvent)
            .then(() => playback_ew.wait_for('canplay')).then(logEvent)
            .then(() => playback_ew.wait_for('canplaythrough')).then(logEvent)
            .then(t.step_func(function() {
                assert_true(video.readyState > HTMLMediaElement.HAVE_CURRENT_DATA);
            }))
            // Now play the file and wait for playback to stall (fire waiting).
            .then(t.step_func(function() {
                video.play();
                // NOTE: setting the wait_for here because we will miss it if we do
                // it after the play call resolves its promise.
                return playback_ew.wait_for('play').then(logEvent);
            }))
            .then(() => playback_ew.wait_for('playing')).then(logEvent)
            // Now observe waiting event and verify readyState
            .then(() => playback_ew.wait_for('waiting')).then(logEvent)
            .then(t.step_func(function(){
                assert_equals(video.readyState, HTMLMediaElement.HAVE_CURRENT_DATA);
            })),

        // timeupdate should fire throughout playback. Make sure we see one.
        waitForRecurringEvent('timeupdate').then(logEvent),

        // progress should fire throughout download. Make sure we see one.
        // Later the download should stall.
        waitForRecurringEvent('progress').then(logEvent)
            .then(() => stalled_ew.wait_for('stalled')).then(logEvent)

    // Verify download and playback resume.
    ]).then(() => Promise.all([
        // Playback should resume when download again makes progress.
        waitForRecurringEvent('progress').then(logEvent),

        // timeupdate should fire throughout playback. Make sure we see one.
        waitForRecurringEvent('timeupdate').then(logEvent),

        // Verify correct sequence of playback events.
        playback_ew.wait_for('canplay').then(logEvent)
            .then(t.step_func(function() {
                assert_true(video.readyState > HTMLMediaElement.HAVE_CURRENT_DATA);
            }))
            .then(() => playback_ew.wait_for('playing')).then(logEvent)
            .then(() => playback_ew.wait_for('canplaythrough')).then(logEvent)
    ])).then(t.step_func_done());


    // Find a supported media file.
    var mediaFile = "content/test.ogv";
    var mimeType = mimeTypeForFile(mediaFile);
    // URL will load part of the file, pause for 8 seconds, then load the rest.
    // The delay of 8 seconds is chosen to reduce flakiness in waiting for the
    // stalled event, which should arrive after roughly 3 seconds of inactivity.
    video.src = "http://127.0.0.1:8000/resources/load-and-stall.php?name=../../../media/" + mediaFile + "&mimeType=" + mimeType + "&stallAt=100000&stallFor=8";

}, "Stalled download pauses playback. When download resumes playback continues. Verify events and readyStates.");
</script>
